Développement de ce site : Étape 3 - Analyse des URLs et Multi-pages
17/09/2020Pour ce troisième billet sur le sujet, j'aborde l'implémentation d'un composant essentiel dans une application Web : le "Router" ; il permet d'analyser l'URL entrante pour déterminer quelle est le code à appeler/executer/retourner.
Tous les Frameworks Web MVC sérieux possèdent un composant qui gère les routes, Plates PHP en tant que micro-framework n'en possède pas,
c'est donc l'occasion d'en développer un sur mesure pour mon site.
Pour rappel, Plates est executé à partir d'un unique fichier qui sert de point d'entrée (index.php), en s'appuyant sur l'URL Rewriting, le Routeur, et plusieurs fichiers de vues, on va chercher dans ce billet à associer des vues différentes à des URLs pour avoir un site multi-pages.
Fonctionnement du Routeur
Input : une URL
La partie interessante de l'URL est la partie entre le nom de domaine et les arguments GET (après le ?) que je vais appeler arguments de routes.
- https://maximeohayon.fr : racine, page d'accueil
- https://maximeohayon.fr/services : services
- https://maximeohayon.fr/billets : tous les billets
- https://maximeohayon.fr/billets/2020-06-13/prestashop-1-5-php-7 : billet spécifique
- https://maximeohayon.fr/billets/2020-06-13/prestashop-1-5-php-7?key=value : billet spécifique
Les arguments de routes sont donc précédés par des "/" et il est possible d'en avoir aucun (racine) ou plusieurs.
Output : une Vue/Page
Sans trop chercher à faire quelques choses de complexe, ce qui va principalement m'interesser est le premier argument mais dans le cas des billets que je n'évoquerai pas ici, il faut pouvoir également récupérer les autres parties.
Le routeur va principalement analyser le premier argument est à partir d'une liste d'expression va retourner le nom de la vue/page à éxecuter.
J'imagine déjà un ensemble de 4 routes pour le site.
- / => index : accueil
- /services => services : mes services
- /realisations => works : mes realisations
- /billets => posts : les billets
Le nom utilisé par la vue peux être différente (cas du works & posts) que l'URL d'entrée, ainsi je décorelle la stratégie SEO (nom des routes) de la mécanique Plates (nom des fichiers de vue).
Redirection
Comme le Routeur se charge déjà d'analyser les URLs, on va le doter de la capacité de déterminer si l'URL appelante doit déboucher sur une redirection.
Le mécanisme de redirection (code de retour HTTP 301) est incontournable si l'on veux suivre la règle Une URL <=> Une Page et éviter ainsi le bête et méchant duplicate content.
De plus, je souhaite garder une relation entre les pages de mon ancien site et celles du nouveau qui justement sont accèssibles par des URLs différentes.
On va utiliser ce mécanisme pour éviter des erreurs 404 et faire comprendre aux moteurs de recherche que la page a changé d'endroit.
J'imagine déja un ensemble de redirections pour le site
- index.php => / : accueil, on ne souhaite pas que le site soit accessible par index.php.
- accueil => / : accueil de mon ancien site.
- index.php?action=services => services : page services de mon ancien site.
- ...
URL Rewritting et Plates
Certes ma configuration Apache/NGINX me permet de faire en sorte que / ou "" (vide) appel index.php, il faut aussi ajouté un bout de code pour que toutes les URLs qui ne soient pas des fichiers (images, css, js, assets,...) pointent aussi vers index.php.
Le Code du Routeur
Initialisation
Le constructeur s'utilise en lui passant la "super-variable" __FILE__
comme argument depuis le index.php, cela permet de se baser à partir du nom réél du fichier et permettre ainsi une exploitation dans un sous dossier.
Ensuite, il va forger quatres objets :
- rootDir : racine à utiliser pour les liens ou pour les images (/ ou /dir dans le cas d'une installation dans un sous dossier).
- requestURI : les arguments de la requêtes sour la forme du chaine de caractères débarassé du rootDir et des arguments GET.
- queryString : les arguments GET sous la forme d'une chaine de caractère.
- computedRoute : la route calculée par la fonction computeRoute() que l'on verra dans le paragraphe suivant
En plus des classiques "getters", on ajoute quatres méthodes qui permettent de manipuler via des tableaux les éléments de routes :
- getRequestURIParts() : retourne sous la forme d'un tableau les différents arguments de routes.
- getRequestURIFirstPart() : retourne le premier élément, si c'est la racine, renvoie "" (vide).
- getRequestURIEndsParts() : retourne les autres éléments du tableau (imputé du premier).
- hasRequestURIEndsParts() : retourne vrai si la requête possèdent plusieurs arguments de routes.
Calcul des Routes
La fonction computeRoute récupère le premier argument de route et fait correspondre à partir du tableau $routes
défini dans la classe la vue correspondante. Dans le cas d'une route non trouvée la fonction retourne la contante NOTFOUND
.
Calcul des Redirections
La fonction shouldRedirect est un peu plus complexe et determine selon plusieurs critères si une redirection doit être réalisée :
- Si l'URL comporte des paramètres GET, on vérifie si une règle de redirection spécifique existe.
- Ensuite, on test sans les paramètres GET.
- Enfin pour éviter que la même ressource soit accessible par
nom
,nom/
ou encorenom//
(et ainsi de suite) on vérifie par une Regexp si une route (et non une redirection) existe. - Pour terminer, de manière plus large on redirige toutes les requêtes ayant plusieurs arguments de requêtes vers le premier argument sauf pour ceux spécifier dans le tableau
$autorizedSubRoutes
. Cela offre une solution pour gérer le cas des sous-routes dans le cadre des billets.
A noter que je n'ai pas besoin d'indiquer le premier /
dans la définition des règles de redirection car il est automatiquement ajouté par $this->rootDir
.
La fonction redirectTo est directement utilisée dans le constructeur pour courcuiter la logique de rendu et rediriger le client avec les bons en-têtes HTTP (code 301). Elle est également utilisable en lui passant une URL spécifique depuis l'exterieur car sa porté est public.
Intégration du Routeur
J'ai fait le choix pour alléger au maximum le fichier index.php de coder le plus de chose dans la classe Router sans toutefois perdre en souplesse. En effet, l'intégration du Routeur est très facile et se fait rééllement en trois lignes de code.
Le simple fait d'instancier l'objet, réalise automatiquement la mécanique d'analyse d'URL, de redirection et de récupération de la vue à afficher par la méthode render()
.
Pour rappel, les fichiers de vues doivent être positionnés dans le dossier templates/views, et s'ils n'existent pas, un jolie message d'erreur Plates (LogicException) fait planter le rendu de la page.
Comme défini dans mes routes, je dois alors avoir 5 templates :
- accueil.php : page d'accueil.
- services.php : page des services.
- works.php : page des réalisations.
- posts.php : page des billets.
- notfound.php : page d'erreur 404 NotFound.
Customisation du Helper pour faire des liens
Dans le billet précédent consacré à cette série, j'avais abordé l'utilisation d'un Helper pour réaliser différentes opérations depuis les vues et notamment pour inclure les fichiers CSS et Javascript. Désormais, on va s'appuyer sur le Router qui à préalablement calculé $rootDir
pour fournir un helper qui fabrique une URL.
Ensuite par exemple pour avoir une URL (href) propre vers la page services, il suffit d'appeler la fonction $helper->linkURL('services')
dans la vue.
En terme d'évolution, on pourrait imaginer laisser le calcul du baseURL
au Router ou encore s'amuser à vérifier si l'URI passée en paramètre nécessite une redirection et ainsi retourner la redirection.
Conclusion
On a pu voir comment développer une mécanique de routing plutôt simple pour associer une route à une page avec un minimum de code à intégrer. De plus, on a vu comment implémenter une politique forte en matière de redirection pour améliorer ou du moins ne pas dégrader le référencement.
Toutefois, je n'ai pas encore abordé la mécanique permettant de gérer les différentes pages pour les billets à savoir ce que j'ai appelé les sous-routes ; cela fera l'objet d'un prochain article spécifique.
Enfin, je tiens à souligner que l'écriture de cette article m'a permis de lever certains problèmes notamment sur les redirections que j'avais mis en place sur mon site en production.